package com.mys.maggie.redis.api.aspect;

import com.mys.maggie.redis.api.config.MagRedisProperties;
import com.mys.maggie.redis.api.config.RedisDistributeService;
import com.mys.maggie.redis.api.spring.MagRedisService;
import com.mys.maggie.redis.api.spring.impl.MagRedisServiceImpl;
import com.mys.maggie.redis.api.util.JackSonUtil;
import io.lettuce.core.RedisCommandExecutionException;
import io.lettuce.core.RedisCommandTimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;


/**
 * Redis API 切面.
 * 切面拦截指定redis接口
 *
 * @date 2020-8-14
 */
@Slf4j
@Aspect
@SuppressWarnings("all")
@Component("redisApiAspect")
public class RedisApiAspect {
    
    @Value("${spring.application.name}")
    private String application;
    
    @Autowired
    private MagRedisProperties properties;
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @Autowired
    private ConfigurableApplicationContext configurableApplicationContext;
    
    private Map<String, RedisTemplate<String, Object>> map;
    
    public Map<String, RedisTemplate<String, Object>> getMap() {
        return map;
    }
    // 用来记录异常出现次数
    private int errorCount = 1;
    
    @Pointcut("execution(public * com.mys.maggie.redis.api.spring.MagRedisService.*(..))")
    private void redisApi() {
    }
    
    @Around("redisApi()")
    public Object doProfiling(ProceedingJoinPoint point) throws Throwable {
        log.info("进入aop===================="+errorCount);
        long initTime = System.currentTimeMillis();
        long sTime = initTime;
        
        String met = null; // 拦截方法名称
        Object args = null; // 拦截的方法参数
        Object ret = null; // 拦截方法返回值
        String clusterName = "";
        if (point.getTarget() instanceof MagRedisServiceImpl) {
            clusterName = ((MagRedisServiceImpl) point.getTarget()).getClusterName();
        }
        try {
            if (point.getArgs().length > 0) {//只取第一个参数
                args = point.getArgs()[0];
            }
            MethodSignature methodSignature = (MethodSignature) point.getSignature();
            met = methodSignature.getName();
            sTime = System.currentTimeMillis();//单独获取系统时间。减少对系统的交互。毫秒值。
            ret = point.proceed();
        } catch (Throwable t) {
            log.error(t.getMessage(), t);
            if(t instanceof RedisSystemException ||
                    t instanceof QueryTimeoutException && t.getCause() instanceof RedisCommandTimeoutException){
                log.info("redis 连不上了=========="+errorCount);
                errorCount ++;
                if (errorCount == 10) {
                    rebuildRedisTemplate();
                    errorCount = 1;
                }
            }
            throw t;//异常向上抛出
        } finally {
        }
        return ret;
    }
    
    void rebuildRedisTemplate() {
        properties.setApplicationName(application);
        this.map = RedisDistributeService.getRedisTemplate(properties);
        
        Map<String, MagRedisService> redisServiceMap = new HashMap<>(8);
        map.forEach((key, value) -> {
            RedisTemplate<String, Object> redisTemplate = map.get(key);
            MagRedisService redisService = new MagRedisServiceImpl(redisTemplate, key);
            redisServiceMap.put(key, redisService);
            //注册 ycRedisService 注解方式
            registerMagRedisService(key, redisTemplate);
        });
    }
    
    private void registerMagRedisService(String key, RedisTemplate<String, Object> redisTemplate) {
        boolean containsBean = applicationContext.containsBean(key);
        //Bean存在先销毁再创建
        if (containsBean) {
            try {
                BeanDefinitionRegistry beanDefinitionRegistry = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
                beanDefinitionRegistry.removeBeanDefinition(key);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        DefaultListableBeanFactory capableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MagRedisServiceImpl.class);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(MagRedisServiceImpl.class);
        ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        constructorArgumentValues.addIndexedArgumentValue(0, redisTemplate);
        constructorArgumentValues.addIndexedArgumentValue(1, key);
        beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
        try {
            capableBeanFactory.registerBeanDefinition(key, beanDefinition);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
